/*
 Parser functions and class implementations for malio interpreter

 For details on specifications of the Malio language, please see:
 https://notabug.org/Malio

 Copyright 2015 - Malio dev team
  
  This file is part of malio-cpp

  malio-cpp is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  malio-cpp is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */

#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <map>
#include "parser.h"
#include "builtins.h"

// reserved keywords:
std::vector<std::string> keywords {
    "print",
    "program",
    "int",
    "char",
    "dec",
    "string",
    "boolean",
    "end",
    "writef",
    "copyf",
    "#",
    "null"
};

// -- Exceptions class definitions:

malio::Exception::Exception(std::string err):
    errortype(err)
{
    std::cout << errortype << ": ";
}

malio::SyntaxError::SyntaxError(std::string what): 
    Exception("SyntaxError"), explanation(what)
{
    std::cout << explanation << std::endl;
}

malio::NameError::NameError(std::string what):
    Exception("NameError"), explanation(what)
{
    std::cout << explanation << " is not defined." << std::endl;
}


// -- Parser functions:

int malio::read(std::string filename)
{
    std::string program_name;
    std::tuple<std::string, std::string> action;
    std::ifstream file(filename);
    if (file.is_open()) {
        std::string buffer;
        while(std::getline(file, buffer)) {
            // Process the line as needed:
            action = malio::parse(buffer);
            if (std::get<0>(action) == "comment")
                continue;
            else if (std::get<0>(action) == "program") {
                program_name = std::get<1>(action);
                continue;
            }
            else if (std::get<0>(action) == "end") {
                // std::cout << "Program exit OK.\n";
                break;
            }
            else if (std::get<0>(action) == "print")
                malio::print(std::get<1>(action));
            else if (std::get<0>(action) == "writef")
                malio::writef(std::get<1>(action), "testwf.txt");
        }
        file.close();
        return 0;
    }
    else {
        std::cerr << "Failed to open file " << filename << std::endl;
        return 1;
    }
}

// take a full line of code and parse it, return the action to perform by
// the interpreter:
std::tuple<std::string, std::string> 
malio::parse(std::string line)
{
    std::stringstream ss(line);
    std::string buffer;
    std::tuple<std::string, std::string> action;
    bool ignore_names(false);

    // When we enter this loop, we need to consider the order of evaluation
    // of things:
    // 1. Is it a comment? Then ignore the rest of the line.
    // 2. Is it a program directive? Then fetch the program information
    // 3. Is it a print statement? Then print the rest of the line. Ignore
    //    names never seen before.
    // 4. Everything else; first come, first served.

    while(ss >> buffer) {
        if (buffer.at(0) == '#') { 
            std::get<0>(action) = "comment"; // tuple syntax is horrible!
            std::get<1>(action) = "";
            break;
        }
        else if (buffer == "program") {
            std::get<0>(action) = "program";
            ignore_names = true;
            continue;
        }
        else if (buffer == "end") {
            std::get<0>(action) = "end";
            std::get<1>(action) = "";
            break;
        }
        else if (buffer == "print") {
            std::get<0>(action) = "print";
            ignore_names = true;
            continue; // fill in the buffer with the remainder of the line.
        }
        else if (buffer == "writef") {
            std::get<0>(action) = "writef";
            ignore_names = true;
            continue;
        }
        else if (
            std::find(
                keywords.begin(), keywords.end(), buffer
            ) == keywords.end() && !ignore_names
        ) {
            // Not in the name database? GTFO!
            throw malio::NameError(buffer);
        }
        else {
            std::get<1>(action).append(buffer);
            std::get<1>(action).append(" ");
        }
    }
    return action;
}
